Skip to content

feat(desktop): add pre-recording upload probe for instant mode#1827

Open
JYZ-LESLIE wants to merge 2 commits into
CapSoftware:mainfrom
JYZ-LESLIE:codex/algora-73-health-probe
Open

feat(desktop): add pre-recording upload probe for instant mode#1827
JYZ-LESLIE wants to merge 2 commits into
CapSoftware:mainfrom
JYZ-LESLIE:codex/algora-73-health-probe

Conversation

@JYZ-LESLIE
Copy link
Copy Markdown

@JYZ-LESLIE JYZ-LESLIE commented May 16, 2026

/claim #73

This implements a scoped upload-speed health check for desktop Instant recordings, and applies a conservative resolution cap before recording starts.

What changed

  • Added a one-shot upload probe in start_recording (Instant mode only):
    • requests a signed upload URL
    • uploads a small payload
    • measures effective upload Mbps
  • Added tier mapping from measured upload speed to recommended max resolution:
    • <4 Mbps -> 720p
    • 4-10 Mbps -> 1080p
    • 10-20 Mbps -> 1440p
    • >=20 Mbps -> 4K
  • Applied resolution cap as min(user_setting, recommended) so user preference remains an upper bound.
  • Added a new RecordingEvent::UploadProbe event and surfaced the probe status in the in-progress recording controls UI.
  • Added focused unit tests for tier mapping, cap behavior, and status classification.

Behavior notes

  • Probe runs once before recording starts and does not re-check during active recording.
  • Probe failures/timeouts fall back to a safe floor recommendation and still allow recording to proceed.

Validation

  • Could not run Rust checks/tests in this environment because cargo is unavailable.
  • Could not run TypeScript checks in this environment because workspace dev dependencies were not installed.

Demo video

  • I cannot record a desktop demo video from this headless environment.

Greptile Summary

This PR adds a one-shot upload-speed probe to Instant mode: before recording starts, a 256 KB payload is PUT to a signed S3 URL, the measured throughput is mapped to a resolution tier, and the lower of the user's setting and the recommended tier is applied. A new UploadProbe event surfaces the result in the in-progress recording UI as a status badge.

  • Probe blocks the recording UI for up to 15 s: run_upload_probe is fully awaited before ShowCapWindow::InProgressRecording is shown, so the user sees no feedback for the entire probe duration on a slow/timeout path — directly at odds with Instant mode's promise.
  • Probe file left in S3: the 256 KB instant-upload-health-probe.bin object is never deleted after measurement, accumulating storage per recording.
  • Event delivery race: UploadProbe is emitted immediately after show(&app).await, which may return before the window's JS has registered its event listener, silently dropping the UI badge.

Confidence Score: 3/5

The resolution cap and probe logic work correctly in the happy path, but the probe runs synchronously before the recording UI appears, which can stall Instant mode for up to 15 seconds on slow or failing connections.

The core correctness of the resolution math and fallback behaviour is solid. The main concern is that awaiting the probe before showing the window breaks the responsiveness of Instant mode. Additionally, each recording leaves a 256 KB orphaned file in S3, and the UploadProbe event is fired in a window where the frontend listener may not yet be active.

apps/desktop/src-tauri/src/recording.rs — specifically the ordering of run_upload_probe relative to ShowCapWindow::InProgressRecording and the missing probe file cleanup.

Important Files Changed

Filename Overview
apps/desktop/src-tauri/src/recording.rs Adds upload probe (signed URL + 256 KB PUT) and resolution cap for Instant mode; probe is awaited before the recording UI window is shown, causing up to 15 s of user-visible hang; probe file left in S3 indefinitely.
apps/desktop/src/routes/in-progress-recording.tsx Adds UploadProbe state and badge UI; camera-disconnected Show block has a stray extra indentation level after the restructure.
apps/desktop/src/utils/tauri.ts Adds UploadProbeStatus type and extends RecordingEvent union — straightforward type-level change, no issues found.
Prompt To Fix All With AI
Fix the following 4 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 4
apps/desktop/src-tauri/src/recording.rs:998-1005
**Probe blocks recording UI for up to 15 seconds**

`run_upload_probe` is fully awaited before `ShowCapWindow::InProgressRecording` is shown (line 1076). If the probe times out, the user gets no visible response for up to `UPLOAD_PROBE_TIMEOUT` (15 s) after clicking record — directly breaking the "instant" promise of this mode. The probe should be launched concurrently with (or after) showing the window, with the result applied asynchronously. If the resolution must be known synchronously, the timeout should be a fraction of a second (e.g. 3–5 s) rather than 15 s.

### Issue 2 of 4
apps/desktop/src-tauri/src/recording.rs:657-660
**Probe payload never cleaned up from S3**

The 256 KB file at `instant-upload-health-probe.bin` is PUT into the video's S3 path via a real signed URL but is never explicitly deleted. Every Instant recording leaves this orphaned object behind, accumulating storage costs and cluttering the video bucket. Consider using a dedicated, separately-namespaced probe path with an S3 lifecycle rule, or delete the object after the measurement completes.

### Issue 3 of 4
apps/desktop/src-tauri/src/recording.rs:1083-1092
**UploadProbe event may fire before the window's JS listener is registered**

`RecordingEvent::UploadProbe` is emitted immediately after `show(&app).await` returns. If `InProgressRecording` is a freshly created window, the JS may still be bootstrapping and the `createEffect` that subscribes to the event will not yet have run. The probe result would be silently dropped and the status badge would never appear. The resolution cap itself is correctly applied server-side, but the UI feedback is lost. Emitting the event after the first `Countdown` tick (when the window is guaranteed to be interactive) would be safer.

### Issue 4 of 4
apps/desktop/src/routes/in-progress-recording.tsx:835-842
**Camera-disconnected block is over-indented**

After the refactor the `Show when={hasCameraInput() && disconnectedInputs.camera}` block is indented one level deeper than its siblings. The visual nesting in the JSX tree no longer matches the actual DOM hierarchy.

Reviews (1): Last reviewed commit: "feat(desktop): add upload probe for inst..." | Re-trigger Greptile

Greptile also left 4 inline comments on this PR.

Comment thread apps/desktop/src-tauri/src/recording.rs Outdated
Comment on lines +998 to +1005
let upload_probe_result = if matches!(inputs.mode, RecordingMode::Instant) {
match video_upload_info.as_ref() {
Some(video) => Some(run_upload_probe(&app, &video.id, user_max_instant_resolution).await),
None => None,
}
} else {
None
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Probe blocks recording UI for up to 15 seconds

run_upload_probe is fully awaited before ShowCapWindow::InProgressRecording is shown (line 1076). If the probe times out, the user gets no visible response for up to UPLOAD_PROBE_TIMEOUT (15 s) after clicking record — directly breaking the "instant" promise of this mode. The probe should be launched concurrently with (or after) showing the window, with the result applied asynchronously. If the resolution must be known synchronously, the timeout should be a fraction of a second (e.g. 3–5 s) rather than 15 s.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src-tauri/src/recording.rs
Line: 998-1005

Comment:
**Probe blocks recording UI for up to 15 seconds**

`run_upload_probe` is fully awaited before `ShowCapWindow::InProgressRecording` is shown (line 1076). If the probe times out, the user gets no visible response for up to `UPLOAD_PROBE_TIMEOUT` (15 s) after clicking record — directly breaking the "instant" promise of this mode. The probe should be launched concurrently with (or after) showing the window, with the result applied asynchronously. If the resolution must be known synchronously, the timeout should be a fraction of a second (e.g. 3–5 s) rather than 15 s.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +657 to +660
crate::api::PresignedS3PutRequest {
video_id: video_id.to_string(),
subpath: "instant-upload-health-probe.bin".to_string(),
method: PresignedS3PutRequestMethod::Put,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Probe payload never cleaned up from S3

The 256 KB file at instant-upload-health-probe.bin is PUT into the video's S3 path via a real signed URL but is never explicitly deleted. Every Instant recording leaves this orphaned object behind, accumulating storage costs and cluttering the video bucket. Consider using a dedicated, separately-namespaced probe path with an S3 lifecycle rule, or delete the object after the measurement completes.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src-tauri/src/recording.rs
Line: 657-660

Comment:
**Probe payload never cleaned up from S3**

The 256 KB file at `instant-upload-health-probe.bin` is PUT into the video's S3 path via a real signed URL but is never explicitly deleted. Every Instant recording leaves this orphaned object behind, accumulating storage costs and cluttering the video bucket. Consider using a dedicated, separately-namespaced probe path with an S3 lifecycle rule, or delete the object after the measurement completes.

How can I resolve this? If you propose a fix, please make it concise.

Comment thread apps/desktop/src-tauri/src/recording.rs Outdated
Comment on lines +1083 to +1092
if let Some(probe) = upload_probe_result.as_ref() {
let _ = RecordingEvent::UploadProbe {
status: probe.status,
upload_mbps: probe.upload_mbps,
recommended_max_resolution: probe.recommended_max_resolution,
applied_max_resolution: probe.applied_max_resolution,
detail: probe.detail.clone(),
}
.emit(&app);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 UploadProbe event may fire before the window's JS listener is registered

RecordingEvent::UploadProbe is emitted immediately after show(&app).await returns. If InProgressRecording is a freshly created window, the JS may still be bootstrapping and the createEffect that subscribes to the event will not yet have run. The probe result would be silently dropped and the status badge would never appear. The resolution cap itself is correctly applied server-side, but the UI feedback is lost. Emitting the event after the first Countdown tick (when the window is guaranteed to be interactive) would be safer.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src-tauri/src/recording.rs
Line: 1083-1092

Comment:
**UploadProbe event may fire before the window's JS listener is registered**

`RecordingEvent::UploadProbe` is emitted immediately after `show(&app).await` returns. If `InProgressRecording` is a freshly created window, the JS may still be bootstrapping and the `createEffect` that subscribes to the event will not yet have run. The probe result would be silently dropped and the status badge would never appear. The resolution cap itself is correctly applied server-side, but the UI feedback is lost. Emitting the event after the first `Countdown` tick (when the window is guaranteed to be interactive) would be safer.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +835 to +842
<Show when={hasCameraInput() && disconnectedInputs.camera}>
<div
class="flex h-8 w-8 items-center justify-center"
title="Camera disconnected - recording continues without camera overlay"
>
<IconLucideVideoOff class="size-5 text-amber-11" />
</div>
</Show>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Camera-disconnected block is over-indented

After the refactor the Show when={hasCameraInput() && disconnectedInputs.camera} block is indented one level deeper than its siblings. The visual nesting in the JSX tree no longer matches the actual DOM hierarchy.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/routes/in-progress-recording.tsx
Line: 835-842

Comment:
**Camera-disconnected block is over-indented**

After the refactor the `Show when={hasCameraInput() && disconnectedInputs.camera}` block is indented one level deeper than its siblings. The visual nesting in the JSX tree no longer matches the actual DOM hierarchy.

How can I resolve this? If you propose a fix, please make it concise.

@JYZ-LESLIE
Copy link
Copy Markdown
Author

Correction (clean formatting):

Addressed the latest Greptile feedback in commit e3a4544:

  • Made upload probing non-blocking for the recording UI: probe now starts in background and no longer blocks opening InProgressRecording.
  • Added a short post-countdown grace window (500ms) for probe resolution, then continues with a safe fallback cap instead of waiting up to 15s.
  • Moved probe object path under a dedicated probes/ namespace (probes/instant-upload-health-probe.bin) to support lifecycle-based cleanup policies.
  • Deferred UploadProbe event emission until after countdown/start-window settle to avoid listener race on freshly created windows.
  • Fixed the over-indented camera-disconnected JSX block for readability/structure consistency.

Validation notes:

  • Rust toolchain (cargo) is not available in this runner, so I could not run Rust-side checks here.
  • Desktop JS test script requires local apps/desktop deps installed in this environment; current runner reports missing local node_modules for that package.

@JYZ-LESLIE
Copy link
Copy Markdown
Author

/claim #73

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🙋 Bounty claim contributor:verified Contributor passed trust analysis.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant